#ifndef __SERVENT_C__
#define __SERVENT_C__

#include "servent.h"

//identificativo dell'utente nella rete chat, configurabile da file
char id[CHAT_ID_LEN];


//inizializza il servent
void servent_init() {
	strcpy(id, get_personal_user_id());
	pthread_mutex_init(&broadcast_mtx, NULL);
	pthread_mutex_init(&node_cache_mtx, NULL);
	packetcache_init();
	peerlist_init();
}


#define set_content_len(r,i)	r->data_len = i

//copia il payload
void net_copy_request(http_req *source, http_req *dest) {
	dest->data_len = source->data_len;
	memcpy(&dest->data[0], &source->data[0], source->data_len);
}


pkt_connect* net_get_connection(connection *c) {
	http_messages *messages = (http_messages*)malloc(sizeof(http_messages));
	net_set_connect(get_node_me(), messages->resp.data);
	receive_http_req(c->sock_s, &messages->req);
	http_request_handler(c->sock_s, messages, &connect_m);
	pkt_connect *pkt = (pkt_connect*)messages->req.data;
	printf("[net_get_connect] id:%d, port:%d\n", pkt->id, pkt->listening_port);
	return pkt;
}

pkt_connect* _net_get_connection(connection *c) {
	http_messages messages;
	net_set_connect(get_node_me(), messages.resp.data);
	receive_http_req(c->sock_s, &messages.req);
	http_request_handler(c->sock_s, &messages, &connect_m);
	pkt_connect *pkt = (pkt_connect*)messages.req.data;
	return pkt;
}

//inizializza il parametro per l'inoltro
void _init_http_forward_p(http_forward_p **hfp_ptr, node *n, http_messages *mes, http_model *mod) {
	*hfp_ptr = (http_forward_p*)malloc(sizeof(http_forward_p));
	http_forward_p *hfp = *hfp_ptr;
	hfp->node = n;
	net_copy_request(&mes->req, &hfp->messages.req);
	clean_headers(hfp->messages.req.headers, http_rqst_header_len);
	hfp->model = mod;
}

//inoltra il pacchetto corrispondente al modello 'mod' contenuto in 'mes' a tutti i nodi ad eccezione di 'n'
void client_flood_forward(void *p) {
	http_forward_p *hfp = (http_forward_p*)p;
	
	lock_nodecache();
	node *server = get_node_me();
	node *cursor;
	for_each_node(cursor) {
		printf("[client_flood_forward] trying to forward to %d\n", cursor->id);
		if(cursor->c->sock_c==0||cursor->c->sock_s==0)
			continue;
		
		if(cursor!=server && cursor!=hfp->node && cursor->id!=0) {
			printf("[client_flood_forward] '%s' to %d\n", network_rqst_method_string[hfp->model->uri], cursor->id);
			lock_client(cursor->c);
			http_request_sender(cursor->c->sock_c, &hfp->messages, hfp->model);
			unlock_client(cursor->c);
		}
	}
	unlock_nodecache();
	
	free(hfp);
	printf("[client_flood_forward] freed http_forward_parameters\n");
}

//inoltra il pacchetto corrispondente al modello 'mod' contenuto in 'mes' al nodo 'n'
void client_forward(void *p) {
	http_forward_p *hfp = (http_forward_p*)p;
	
	printf("[client_forward] forwarding '%s' to %d, datasize:%d, guid:",network_rqst_method_string[hfp->model->uri], hfp->node->id, hfp->messages.req.data_len);printf_guid(hfp->messages.req.data); printf("\n");
	lock_client(hfp->node->c);
	http_request_sender(hfp->node->c->sock_c, &hfp->messages, hfp->model);
	unlock_client(hfp->node->c);

	free(hfp);
}

//inoltra il pacchetto corrispondente al modello 'mod' contenuto in 'mes' a tutti i nodi ad eccezione di 'n'
void client_flood_forward_thread(node *n, http_messages *mes, http_model *mod) {
	http_forward_p *hfp = (http_forward_p*)malloc(sizeof(http_forward_p));
	
	//inizializzo i parametri necessari alla copia
	hfp->node = n;
	hfp->model = mod;
	
	//copio headers e payload
	net_copy_request(&mes->req, &hfp->messages.req);
	clean_headers(hfp->messages.req.headers, http_rqst_header_len);
	copy_http_headers(hfp->messages.req.headers, mes->req.headers);	
	
	pthread_t h;
	pthread_create(&h,NULL,client_flood_forward,(void*)hfp);
	pthread_detach(h);
	
	printf("[client_flood_forward_thread] got freedom\n");
	return;
}

//inoltra il pacchetto corrispondente al modello 'mod' contenuto in 'mes' al nodo 'n'
void client_forward_thread(node *n, http_messages *mes, http_model *mod) {
	http_forward_p *hfp = (http_forward_p*)malloc(sizeof(http_forward_p));
	//inizializzo i parametri necessari alla copia
	hfp->node = n;
	hfp->model = mod;
	//copio headers e payload
	net_copy_request(&mes->req, &hfp->messages.req);
	clean_headers(hfp->messages.req.headers, http_rqst_header_len);
	copy_http_headers(hfp->messages.req.headers, mes->req.headers);
	

	pthread_t h;
	pthread_create(&h,NULL,client_forward,(void*)hfp);
	pthread_detach(h);
	
	printf("[client_forward_thread] got freedom\n");
	return;
}

//stabilisce la connessione a livello network con il nodo 'n'
void client_connect(node *n) {
	net_set_connect(get_node_me(), n->client.req.data);
	n->status |= http_request_sender_default(n->c->sock_c, &n->client,&connect_m);
	net_read_connect(n, n->client.resp.data);
}

//avvia la disconnessione da tutti i nodi presenti nella nodecache
void client_disconnect_all() {
	printf("[client_disconnect_all]begin\n");
	
	lock_nodecache();
	node *closing = listener;
	listener = NULL;
	do_close_server(closing->c);
	unlock_nodecache();
	
	lock_nodecache();
	node *server = get_node_me();
	node *cursor;
	save_bootstrap_list(config.user_id);
	for_each_node(cursor) {
		lock_node(cursor);
		printf("[client_flood_forward] trying to disconnect f %d\n", cursor->id);
		if(cursor!=server && cursor->id!=0) {
			client_disconnect(cursor);
		}
		unlock_node(cursor);
	}
	unlock_nodecache();
	
	while(get_nodecache_clients_l()!=0) {
		sleep(1);
		printf_nodecache();
	}
}

//avvia la disconnessione da tutti i client
void client_disconnect(node *n) {
	lock_client(n->c);
	if(valid_socket(n->c->sock_c)) {
		printf("[client_disconnect]valid connection %d\n", n->c->sock_c);
		n->status |= http_request_sender_default(n->c->sock_c, &n->client,&disconnect_m);
		do_close_client(n->c);
	}
	unlock_client(n->c);
}

//gestisce lo scambio di pacchetti lato client relativi ad un messaggio di ping con il nodo 'n'
void client_ping(node *n) {
	lock_client(n->c);
	net_build_header(get_node_me(), &n->client.req.data);
	http_request_sender_default(n->c->sock_c, &n->client, &ping_m);
	unlock_client(n->c);
}

void client_ping_all() {
	http_messages hm;
	net_build_header(get_node_me(), hm.req.data);
	hm.req.data_len = sizeof(pkt_header);
	clean_all_headers(&hm);
	client_flood_request(NULL, &hm, &ping_m);
}

//gestisce lo scambio di pacchetti lato client relativi ad un messaggio di modello 'model' con il nodo 'n'
void client_send(node *n, http_model *model) {
	net_build_header(get_node_me(), &n->client.req.data);
	http_request_sender(n->c->sock_c, &n->client, model);
}

//gestisce lo scambio di pacchetti lato server relativi ad un messaggio di pong con il nodo 'n'
void server_pong(node *n) {
	if(server_flood_response(n, &pong_m)) {
		printf("MY PONG!\n");
		peerlist_add(net_get_pong(n->server.req.data));
	}
}

//gestisce lo scambio di pacchetti lato client relativi ad un messaggio di pong con il nodo 'n'
void client_pong(node *nd) {
	node *n = (node*)nd;
	lock_client(n->c);
	node *me = get_node_me();
	net_build_header(me, n->client.req.data);
	net_set_pong(n, n->client.req.data);
	http_request_sender_default(n->c->sock_c, &n->client, &pong_m);
	unlock_client(n->c);
	return;
}

int client_flood_request(node *source, http_messages *mes, http_model *model) {
	node *server = get_node_me();
	node *n;
	for_each_node(n) {
		if(n==server || n==source)
			continue;
		if(n->c->sock_c==0||n->c->sock_s==0)
			continue;
		
		printf("[client_flood_request] flooding a request\n");
		lock_client(n->c);
		http_request_sender(n->c->sock_c, mes, model);
		unlock_client(n->c);
	}
}

//funzioni per l'invio di pacchetti relativi all'applicazione chat
int chat_notify_status(char *status) {
	http_messages hm;
	
	net_build_header(get_node_me(), hm.req.data);
	hm.req.data_len = sizeof(pkt_header);
	
	clean_all_headers(&hm);
	set_header(hm.req.headers, HTTP_RQST_HEAD_UID, id);
	set_header(hm.req.headers, HTTP_RQST_HEAD_NICK, get_personal_user_name());
	set_header(hm.req.headers, HTTP_RQST_HEAD_STATUS, get_personal_status());
	
	printf("[chat_notify_status] packet built\n");
	
	client_flood_request(NULL, &hm, &status_m);
}

int chat_get_list(char *chat_name) {
	http_messages hm;
	
	net_build_header(get_node_me(), hm.req.data);
	hm.req.data_len = sizeof(pkt_header);
	
	clean_all_headers(&hm);
	
	set_header(hm.req.headers, HTTP_RQST_HEAD_UID, id);
	set_header(hm.req.headers, HTTP_RQST_HEAD_NICK, get_personal_user_name());
	set_header(hm.req.headers, HTTP_RQST_HEAD_CHAT, chat_name);

	client_flood_request(NULL, &hm, &list_m);
}

void chat_send_message(char *chat_name, char *message) {
	http_messages hm;

	net_build_header(get_node_me(), hm.req.data);
	net_build_message(message, &hm.req);
	
	clean_all_headers(&hm);
	set_header(hm.req.headers, HTTP_RQST_HEAD_NICK, get_personal_user_name());
	set_header(hm.req.headers, HTTP_RQST_HEAD_UID, id);
	set_header(hm.req.headers, HTTP_RQST_HEAD_CHAT, chat_name);
	set_header(hm.req.headers, HTTP_RQST_HEAD_STATUS, get_personal_status());
	client_flood_request(NULL, &hm, &message_m);
}

void chat_leave(char *chat_name) {
	http_messages hm;
	
	net_build_header(get_node_me(), hm.req.data);
	hm.req.data_len = sizeof(pkt_header);
	
	clean_all_headers(&hm);
	set_header(hm.req.headers, HTTP_RQST_HEAD_UID, id);
	set_header(hm.req.headers, HTTP_RQST_HEAD_NICK, get_personal_user_name());
	set_header(hm.req.headers, HTTP_RQST_HEAD_CHAT, chat_name);

	client_flood_request(NULL, &hm, &leave_m);
}

void chat_join(char *chat_name) {	
	http_messages hm;
	
	net_build_header(get_node_me(), hm.req.data);
	hm.req.data_len = sizeof(pkt_header);
	
	clean_all_headers(&hm);
	set_header(hm.req.headers, HTTP_RQST_HEAD_UID, id);
	set_header(hm.req.headers, HTTP_RQST_HEAD_NICK, get_personal_user_name());
	set_header(hm.req.headers, HTTP_RQST_HEAD_CHAT, chat_name);
	set_header(hm.req.headers, HTTP_RQST_HEAD_STATUS, get_personal_status());
	client_flood_request(NULL, &hm, &join_m);
}

//restituisce il messaggio contenuto nel pacchetto puntato da 'data'
char* chat_read_message(void *data) {
	chat_message *cm = (chat_message*)(data + sizeof(pkt_header));
	
	if(strlen(cm->message)>=cm->len)
		printf("MESSAGE WITH BAD LEN: %s\n", cm->message);
	return cm->message;
}

//gestisce un richiesta di tipo 'model' dal nodo 'n' e chiama le funzioni per il flooding
int server_flood_request(node *n, http_model *model) {
	http_request_handler(n->c->sock_s, &n->server, model);
	if(net_modify_header(n, n->server.req.data)!=NET_VALID_PACKET) {
		printf("[server_flood_req]invalid packet!\n");
		return 0;
	}
	if(net_set_owner(n, n->server.req.data)!=NULL) {
		printf("[server_flood_req]present guid\n");
		return 0;
	}
	printf("[server_flood_req]forwarding to everyone\n");
	client_flood_forward_thread(n, &n->server, model);
	return 1;
}

//gestisce un risposta di tipo 'model' dal nodo 'n' e chiama le funzioni per il flooding
int server_flood_response(node *n, http_model *model) {
	http_request_handler(n->c->sock_s, &n->server, model);
	if(net_modify_header(n, n->server.req.data)!=NET_VALID_PACKET) {
		printf("[server_flood_resp]invalid packet!\n");
		return 0;
	}
	node *dest = net_get_owner(n->server.req.data);
	if(dest==NULL) {
		printf("[server_flood_resp]not present guid\n");
		return 0;
	}
	if(dest==get_node_me()) {
		printf("[server_flood_resp]my resp!\n");
		return 1;
	}
	printf("[server_flood_resp]not for me, forwarding\n");
	client_forward_thread(dest, &n->server, model);
	return 0;
}

//gestisce lo scambio di pacchetti lato server relativi ad un messaggio di ping con il nodo 'n'
void server_ping(node *n) {
	if(!server_flood_request(n, &ping_m))
		return;
	
	net_build_header_response(n, n->server.req.data);
	net_set_pong(n, &n->server.req);
	client_forward_thread(n, &n->server, &pong_m);
	return;
}

//gestisce lo scambio di pacchetti lato server relativi ad un messaggio di list con il nodo 'n'
void server_list(node *n) {
	if(!server_flood_request(n, &list_m))
		return;
		
	printf("[chat_list]i am in chat %s? %d\n", get_header(n->server.req.headers, HTTP_RQST_HEAD_CHAT),check_joined_chat(get_header(n->server.req.headers, HTTP_RQST_HEAD_CHAT)));
	
	if(!check_joined_chat(get_header(n->server.req.headers, HTTP_RQST_HEAD_CHAT))) {
		return;
	}
	
	net_build_header_response(n, n->server.req.data);
	set_header(n->server.req.headers, HTTP_RQST_HEAD_UID, id);
	set_header(n->server.req.headers, HTTP_RQST_HEAD_STATUS, get_personal_status());
	set_header(n->server.req.headers, HTTP_RQST_HEAD_NICK, get_personal_user_name());
	set_header(n->server.req.headers, HTTP_RQST_HEAD_CHAT, get_header(n->server.req.headers, HTTP_RQST_HEAD_CHAT));
	client_forward_thread(n, &n->server, &listhit_m);
	return;
}

void server_handle_flood_request(node *node, http_model *model) {
	if(!server_flood_request(node, model))
		return;

	client_forward_thread(node, &node->server, (http_model*)model->net_response);
	return;
}

//server http associato al lato server della connection di 'n'
void net_http_server(node *n) {
	http_req *req = &n->server.req;
	http_resp *resp = &n->server.resp;
	
	while(1) {
		printf("[net_http_server]receiving the http request..\n");
		clean_headers(req->headers, HTTP_MAX_HEADERS);
		receive_http_req(n->c->sock_s, req);
		switch(req->uri) {
			case NETWORK_RQST_METHOD_PING:
				server_ping(n);
				break;
				
			case NETWORK_RQST_METHOD_PONG:
				printf("PONG RECEIVED!\n");
				server_pong(n);
				break;
			
			case NETWORK_RQST_METHOD_DISCONNECT:
				printf("DISCONNECT RECEIVED!\n");
				http_request_handler(n->c->sock_s, &n->server,&disconnect_m);
				printf("LOCKING..");
				lock_nodecache();
				printf("LOCKED NODECACHE..");
				lock_node(n);
				printf("LOCKED NODE..");
				client_disconnect(n);
				printf("client disconnected..");
				do_close_server(n->c);
				printf("deleting node %x..\n", n->id);
				delete_node(n);
				unlock_nodecache();
				printf("DISCONNECTED\n");
				return;
				break;
			
			//----APPLICATION---------
			case NETWORK_RQST_METHOD_MESSAGE:
				if(!server_flood_request(n, &message_m))
					continue;
				printf("[net_http_server]message:'%s'\n", chat_read_message(n->server.req.data));
				incoming_message(get_header(n->server.req.headers, HTTP_RQST_HEAD_CHAT),get_header(n->server.req.headers, HTTP_RQST_HEAD_UID), get_header(n->server.req.headers, HTTP_RQST_HEAD_NICK), get_header(n->server.req.headers, HTTP_RQST_HEAD_STATUS), chat_read_message(n->server.req.data));
					
					if(strcmp(chat_read_message(n->server.req.data), "/disconnect")==0) {
						
						net_disconnect();
						
					}
				break;
				
			case NETWORK_RQST_METHOD_STATUS:
				if(!server_flood_request(n, &status_m))
					continue;
				
				printf("[net_http_server]status:'%s', uid:'%s'\n", get_header(n->server.req.headers, HTTP_RQST_HEAD_STATUS), get_header(n->server.req.headers, HTTP_RQST_HEAD_UID));
				update_contact_status(get_header(n->server.req.headers, HTTP_RQST_HEAD_UID),get_header(n->server.req.headers, HTTP_RQST_HEAD_STATUS));
				
				break;
				
			case NETWORK_RQST_METHOD_LEAVE:
				if(!server_flood_request(n, &leave_m))
					continue;
				printf("[net_http_server]id %s leaved chat\n",get_header(n->server.req.headers, HTTP_RQST_HEAD_UID));
				rem_contact(get_header(n->server.req.headers, HTTP_RQST_HEAD_CHAT),get_header(n->server.req.headers, HTTP_RQST_HEAD_UID));
				break;
				
			case NETWORK_RQST_METHOD_LIST:
				server_list(n);
				break;
				
			case NETWORK_RQST_METHOD_LISTHIT:
				if(server_flood_response(n, &listhit_m)) {
					printf("LISTHIT: %s\n", get_header(n->server.req.headers, HTTP_RQST_HEAD_NICK));
					add_contact(get_header(n->server.req.headers, HTTP_RQST_HEAD_CHAT),get_header(n->server.req.headers, HTTP_RQST_HEAD_UID), get_header(n->server.req.headers, HTTP_RQST_HEAD_NICK), get_header(n->server.req.headers, HTTP_RQST_HEAD_STATUS));
				}
				
				break;

			case NETWORK_RQST_METHOD_JOIN:
				if(!server_flood_request(n, &join_m))
					continue;
				add_contact(get_header(n->server.req.headers, HTTP_RQST_HEAD_CHAT),get_header(n->server.req.headers, HTTP_RQST_HEAD_UID), get_header(n->server.req.headers, HTTP_RQST_HEAD_NICK), get_header(n->server.req.headers, HTTP_RQST_HEAD_STATUS));
				break;
			default:
				printf("!!unknown network method\n");
		}
	}
	return;
}

void net_http_client(node *n) {
	printf("NETWORK CONNECTION SUCCESSFUL\n");
}

void net_disconnect() {
	node *me = get_node_me();
	client_flood_forward_thread(me, &me->client, &disconnect_m);
}

#endif
